"""
和客户端保持长连接的切片策略服务器
"""
import os
import socket
from threading import Thread
import time
import c_arima
import QoEcal
import numpy as np
import sliceUtil as sutil
import ip_conf

# g_addr开头的hash表中，key是(ip, port)这一socket，代表一个用户
g_addr_reSlQo = {} # value是该用户的[需求类型，优先级，分配带宽]

g_addr_qoe = {} # value是该addr的[qoelist]，从开始运行开始一直记录。
g_addr_netcond = {} # value是该addr的[bandwidth, delay, jitter, pl], 用于QoE展示
g_addr_slicetime = {} # 记录开始切片的时间
g_addr_videoPort = {}
# g_net开头的是记录网络状态的变量。
# 默认优先级为2和6两种。引入用户权重的话则可令尊贵的客户为1和5（越小越优先）
g_net_bandsum = 100 # 系统总带宽100Mbit
g_net_sliaddr = {} # 以优先级类型为key，该优先级下的addrlist为value，存分配给切片类型的的用户ip们
g_net_slires = {} # 以优先级类型为key，已分配给该优先级的带宽为value
g_net_bandinuse = 0 # 目前使用中的总贷款
# 是否要记录每个优先级目前的平均时延和抖动？暂时不用吧

# tc工具的flowid分配，从3开始
g_tcflowid = 3
g_addr_flowid = {} # 某个socket对应的flowid

DEBUG = 1


# 初始化切片策略服务器
def init():
    global g_net_sliaddr, g_net_slires
    g_net_sliaddr[1] = []
    g_net_sliaddr[2] = [] # 2和6两个优先级下的ip和分配带宽目前均为空。
    g_net_sliaddr[6] = []
    g_net_sliaddr[3] = [] # 2和6两个优先级下的ip和分配带宽目前均为空。
    g_net_sliaddr[4] = []
    g_net_sliaddr[5] = [] # 2和6两个优先级下的ip和分配带宽目前均为空。
    g_net_slires[1] = 0
    g_net_slires[2] = 0
    g_net_slires[6] = 0
    g_net_slires[3] = 0
    g_net_slires[4] = 0 
    g_net_slires[5] = 0
    try:
        socketServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except:
        print('服务器创建失败')
        exit()
    socketServer.bind((ip_conf.sli_ip, ip_conf.sli_port))
    socketServer.listen(10)
    print('切片策略服务器开始运行')
    initsh = ip_conf.sh_dir + "fine_init_tc.sh"
    os.system(initsh)
    print('Traffic control 初始化成功')
    return socketServer
       
g_isClient2 = 0
# 处理切片请求的函数（做出决策）
def dealReq(conn, addr): # 通过conn操作该socket，addr是(ip, port)
    global g_addr_reSlQo, g_addr_qoe, g_addr_videoPort, g_isClient2
    global g_net_bandsum, g_net_bandinuse, g_net_sliaddr, g_net_slires, g_tcflowid, g_addr_flowid
    print('Connected by', addr)
    ipfrom = addr[0]
    # portFrom = addr[1]
    while 1 :
        try:
            rcvmsg = ""
            while 1:
                t = conn.recv(9192)
                msg = t.decode()
                rcvmsg += msg
                if rcvmsg.find('END') != -1: # 消息结尾
                    break
            idx_tp = rcvmsg.find('type=')
            if idx_tp == -1:
                continue
            tp = int(rcvmsg[idx_tp + 5])
            
            if tp == 1:  # 建立切片
                req, ubw, dbw, dbm, Rsrq, band, pack, delay, jitter, pt= sutil.parsemsg(rcvmsg, tp)
                g_addr_videoPort[addr] = pt
                bandfor = band # 暂存一下拿来预测
                qoelist = QoEcal.get_QoE(band, delay, jitter, pack, req)
                g_addr_netcond[addr] = [band, delay, jitter, pack]
                g_addr_qoe[addr] = qoelist
                # 现在是带宽预测结和按需匹配优先级的策略：
                # 预测
                forecastRes = c_arima.forecast(bandfor, len(bandfor) - 1).tolist() # 得到预测的x秒贷款
                # forecastRes = 100
                min_f_band = min(forecastRes)
                max_f_band = max(forecastRes)
                forecastRes.pop(forecastRes.index(min_f_band))
                forecastRes.pop(forecastRes.index(max_f_band))
                max_f_band = np.max(forecastRes)
                mean_f_band = np.mean(forecastRes)
                # 结合需求调整优先级与带宽
                poss = 6
                sliceband = round((mean_f_band/3 + max_f_band*2/3), 1)
                # print(req, sliceband)
                if req == 2 or req == 3:
                    poss = 2
                if req == 1 or req == 3:
                    if sliceband < 28: # 拥塞路段的切片之前的可能和实际需求差距较大，后面调整。
                        sliceband = 33
                    else:
                        sliceband += 20
                elif req == 0 or req == 2:
                    if sliceband < 5: # 切片之前的可能和实际需求差距较大，后面调整。
                        sliceband = 6
                    else:
                        sliceband += 10
                # if g_isClient2 == 0:
                #     sliceband = 8
                #     g_isClient2+=1
                #     # poss-=1  # 优先级更高
                # elif g_isClient2 == 1:
                #     sliceband = 15
                #     g_isClient2+= 1
                else:
                    print(f'为 用户{ipfrom}不设置切片')
                    break # 不给切片
            # 是不是应该尝试增加第三个不切片的看看他没有享用切片的话能不能被保障
                sliceband = 100
                os.system(ip_conf.sh_dir + 'fine_slice.sh' + ' ' + str(g_tcflowid) + ' ' + str(poss) + ' ' + str(sliceband) + ' ' + str(ipfrom))
                print(f'为 用户{ipfrom} 设置优先级{poss}带宽{sliceband}的切片,id为{g_tcflowid}')
                g_addr_slicetime[addr] = len(band)
                g_addr_reSlQo[addr]= [req, poss, sliceband]

                # 更新结果
                g_addr_flowid[addr] = g_tcflowid # 更新全局flowid
                g_tcflowid += 1

                g_net_sliaddr[poss].append(addr) # 在该优先级的addrlist中加入该addr
                g_net_slires[poss] = g_net_slires[poss] + sliceband # 更新该优先级使用带宽的结果
                g_net_bandinuse += sliceband # 加上新占用的带宽
                print(str(g_addr_reSlQo))

            elif tp == 0: # 已建立切片后终端传的内容 更新QoE和网络状态
                band, pack, delay, jitter= sutil.parsemsg(rcvmsg, tp) # 可以这里再接一个预测去作调整
                g_addr_netcond[addr][0] = sutil.updatelist(g_addr_netcond[addr][0], band)  # 带宽
                g_addr_netcond[addr][1] = sutil.updatelist(g_addr_netcond[addr][1], delay)  # 时延
                g_addr_netcond[addr][2] = sutil.updatelist(g_addr_netcond[addr][2], jitter)  # 抖动
                g_addr_netcond[addr][3] = sutil.updatelist(g_addr_netcond[addr][3], pack)  # 丢包率

                qoelist = QoEcal.get_QoE(band, delay, jitter, pack, g_addr_reSlQo[addr][0]) # 最后一项是通过该用户预先的req计算
                g_addr_qoe[addr] = sutil.updatelist(g_addr_qoe[addr], qoelist) # qoe list 往后续上
            
            elif tp == 2: # 释放切片的操作
                # 将该用户的qoe和网络变化情况都记录下来
                print(f'释放用户{ipfrom}： 需求类型:{g_addr_reSlQo[addr][0]}，切片优先级:{g_addr_reSlQo[addr][1]}, 切片带宽:{g_addr_reSlQo[addr][2]}')

                # sutil.saveResfig(addr, g_addr_qoe, g_addr_netcond, g_addr_slicetime)
                resolveSlice(addr, g_addr_videoPort[addr])
                break

        except ConnectionResetError:
            conn.close()
            break

'''
释放切片和定时判断是否要释放切片的函数
'''
# 释放切片资源，并记录该切片实例可复用（未完全完成）。
def resolveSlice(addr, pt):
    global g_addr_reSlQo, g_addr_qoe, g_addr_netcond, g_addr_slicetime, g_addr_flowid
    global g_net_sliaddr, g_net_slires, g_net_bandinuse
    ipfrom = addr[0]
    portFrom = addr[1]
    if g_addr_reSlQo.get(addr, 0) != 0: # 已经分配过切片的
        prio = g_addr_reSlQo[addr][1] # 获取优先级类型
        bandresolve = g_addr_reSlQo[addr][2]
        flowid = g_addr_flowid[addr] # 获取切片资源的id
        print(f'为 {ipfrom}:{portFrom}(对应音视频port:{pt}) 删除优先级{prio}带宽{bandresolve}的切片,id为{flowid}') 
        # 删除切片
        strdel = ip_conf.sh_dir + 'fine_delFilter.sh' + ' ' + str(flowid) + ' ' + str(ipfrom) + ' ' + str(portFrom)
        print('删除指令：' + strdel)
        os.system(ip_conf.sh_dir + 'fine_delFilter.sh' + ' ' + str(flowid) + ' ' + str(ipfrom) + ' ' + str(portFrom))
        g_addr_flowid.pop(addr)
        g_addr_reSlQo.pop(addr)
        g_addr_qoe.pop(addr)
        g_addr_netcond.pop(addr)
        g_addr_slicetime.pop(addr)
        # 更新网络状态
        g_net_bandinuse -= bandresolve
        g_net_slires[prio] -= bandresolve
        g_net_sliaddr[prio].remove(addr)


if __name__ == '__main__':
    # 初始化切片策略服务器
    socketSer = init()
    while True:
        try:
            conn, addr = socketSer.accept()
        except:
            print('接收终端通信失败。')
            break
        Thread(target=dealReq, args=(conn, addr)).start()
    socketSer.close()


    # Thread(target=monitor_con).start()
    # Thread(target=getinput).start()

# @ deprecated
# g_addr_qoemean = {} # value是该addr的最近一次qoe均值
# g_addr_qoemeanlist = {} # value是该addr的qoe均值的list
# g_addr_lastconnect = {} # 超时触发器 若很久没有再次上报 则释放切片资源。（MARK：暂时没必要的功能）
# 在删除切片的函数中使用的变量。由于tc工具的class没法删除，因此当新的用户需求符合该切片时，可将该flowid对于的切片复用。（MARK：暂时没必要的功能）
# g_sliceRemain = {} # key是prio，value是[band, flowid]的list
'''
查看qoe及结果相关的函数
'''
# # 主线程接收终端输入，然后输出用户优先级和带宽，及QoE结果
# def getinput():
#     global g_addr_reSlQo
#     print('输入1查看qoe变化情况')
#     while 1:
#         cmd = input()
#         if cmd == '1':
#             for k in g_addr_reSlQo.keys(): # 有接入用户
#                 print(f'用户{k[0]}： 需求类型:{g_addr_reSlQo[k][0]}，切片优先级:{g_addr_reSlQo[k][1]}, 切片带宽:{g_addr_reSlQo[k][2]}')
#                 print("QoE变化情况：")
#                 printUserQoe(k)
#                 print('')
# 输出某个用户的QoE情况。
# def printUserQoe(ipfrom):
#     global g_addr_qoemeanlist
#     for i in g_addr_qoemeanlist[ipfrom]:
#         print(i, end=' ')
#     print('')

# 定时器，超时时取消切片分配
# def monitor_con():
#     global g_addr_lastconnect
#     toresolve = []
#     while 1:
#         time.sleep(1)
#         keys = g_addr_lastconnect.keys()
#         for k in keys:
#             g_addr_lastconnect[k] = g_addr_lastconnect[k] + 1
#             # if(DEBUG):print(f"监视 count：{k}:{g_addr_lastconnect[k]}")
#             if g_addr_lastconnect[k] > 150: # 暂时认为150s+未发送信息的已经断开应用
#                 toresolve.append(k)
#         for i in toresolve:
#             resolveSlice(i)
